// Created by Roman Novitsky on 29.05.2009


#region Imported Namespaces


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Xml;

using AvalonDock;

using WtfManager.Controls;
using WtfManager.Framework;


#endregion


namespace WtfManager.UIManager
{
  public class DockingManagerStateManagerExtension : ExtensibleService, IStateManagerExtension
  {
    #region Fields


    private UIService _uiService;


    #endregion


    #region Properties/Indexers


    public override string Description
    {
      get
      {
        return "Manages Docking Manager States.";
      }
    }

    public override string Name
    {
      get
      {
        return "Docking Manager State Extension";
      }
    }


    #endregion


    #region Methods


    public void RestoreState(XmlNode stateNode, FrameworkElement frameworkElement)
    {
      const string invalidStructureMessage = "State file has an invalid structure!";

      var dockingManager = (DockingManager)frameworkElement;

      if (stateNode.ChildNodes.Count != 3 || stateNode.ChildNodes[0].Name != "OpenPanels"
          || stateNode.ChildNodes[1].Name != "OpenDocuments" || stateNode.ChildNodes[2].Name != "DockingManager")
      {
        Debug.Fail(invalidStructureMessage);
        return;
      }

      LoadOpenPanels(stateNode.ChildNodes[0], dockingManager);
      LoadOpenDocuments(stateNode.ChildNodes[1], dockingManager);
      RestoreDockingManagerLayout(stateNode.ChildNodes[2], dockingManager);
    }


    private static void RestoreDockingManagerLayout(XmlNode dickingManagerNode, DockingManager dockingManager)
    {
      var xmlDocument = new XmlDocument();
      xmlDocument.ImportNode(dickingManagerNode, true);
      xmlDocument.InnerXml = dickingManagerNode.OuterXml;

      dockingManager.RestoreLayout(xmlDocument);
    }


    private void LoadOpenPanels(XmlNode openPanelsNode, DockingManager dockingManager)
    {
      foreach (XmlNode panelNode in openPanelsNode.ChildNodes)
      {
        XmlAttribute typeAttribute = panelNode.Attributes["TypeName"];
        string typeName = typeAttribute.Value;
        string panelName = panelNode.Name;

        UIControlInfo panel = _uiService.GetPanel(typeName);

        if (panel == null)
        {
          continue;
        }

        var dockableContent = (DockableContent)panel.CreateInstance();
        dockableContent.Name = panelName;
        dockingManager.Show(dockableContent, DockableContentState.FloatingWindow);
      }
    }


    private void LoadOpenDocuments(XmlNode openDocumentsNode, DockingManager dockingManager)
    {
      CloseAllOpenDocuments(dockingManager);

      DocumentPane currentDocumentPane = dockingManager.MainDocumentPane;

      foreach (XmlNode documentNode in openDocumentsNode.ChildNodes)
      {
        string nodeName = documentNode.Name;

        if (nodeName == "DocumentPaneResizingPanel")
        {
          string orientation = documentNode.Attributes["Orientation"].Value;

          LoadOpenDocuments(documentNode, dockingManager, currentDocumentPane, orientation);
        }
        else
        {
          LoadOpenDocuments(documentNode, currentDocumentPane);
        }
      }
    }


    private static void CloseAllOpenDocuments(DependencyObject dockingManager)
    {
      List<DocumentPane> documentPanes = GetAllDocumentPanes(dockingManager);

      foreach (DocumentPane documentPane in documentPanes)
      {
        var items = new object[documentPane.Items.Count];
        documentPane.Items.CopyTo(items, 0);

        for (int i = 0; i < items.Length; i++)
        {
          object item = items[i];

          if (!(item is DocumentContent))
          {
            continue;
          }

          var content = (DocumentContent)item;
          content.Close();
        }
      }
    }


    private void LoadOpenDocuments(XmlNode resizingPanelNode, DockingManager dockingManager, DocumentPane documentPane,
                                   string orientation)
    {
      AnchorStyle anchorStyle = orientation == "Vertical" ? AnchorStyle.Bottom : AnchorStyle.Right;
      XmlNode nestedResizingPanelNode = null;

      foreach (XmlNode documentNode in resizingPanelNode.ChildNodes)
      {
        string nodeName = documentNode.Name;

        if (nodeName == "DocumentPaneResizingPanel")
        {
          nestedResizingPanelNode = documentNode;
        }
      }

      if (nestedResizingPanelNode != null)
      {
        LoadOpenDocuments(nestedResizingPanelNode, dockingManager, documentPane,
                          nestedResizingPanelNode.Attributes["Orientation"].Value);
      }

      bool loadDocumentsIntoCurrentPane = nestedResizingPanelNode == null;

      foreach (XmlNode documentNode in resizingPanelNode.ChildNodes)
      {
        string nodeName = documentNode.Name;

        if (nodeName == "DocumentPane")
        {
          DocumentPane loadedPane;

          if (loadDocumentsIntoCurrentPane)
          {
            LoadOpenDocuments(documentNode, documentPane);
            loadDocumentsIntoCurrentPane = false;
            loadedPane = documentPane;
          }
          else
          {
            loadedPane = new DocumentPane();
            LoadOpenDocuments(documentNode, loadedPane);

            dockingManager.Anchor(loadedPane, documentPane, anchorStyle);
          }

          loadedPane.Name = documentNode.Attributes["Name"].Value;
        }
      }
    }


    private void LoadOpenDocuments(XmlNode documentPaneNode, ItemsControl documentPane)
    {
      foreach (XmlNode documentNode in documentPaneNode.ChildNodes)
      {
        string typeName = documentNode.Name;

        UIControlInfo contentContainer = _uiService.GetContentContainer(typeName);

        if (contentContainer == null)
        {
          return;
        }

        var documentContent = (DocumentContent)contentContainer.CreateInstance();
        LoadDocumentContent(documentContent, documentNode["Content"]);

        ItemCollection items = documentPane.Items;
        items.Insert(items.Count, documentContent);
      }
    }


    private void LoadDocumentContent(DocumentContent documentContent, XmlNode contentNode)
    {
      foreach (IContentSerializer serializer in Extensions)
      {
        if (serializer.CanDeserialize(contentNode))
        {
          object data = serializer.Deserialize(contentNode);

          SetDocumentContent(documentContent, data);
        }
      }
    }


    public void SaveState(XmlTextWriter writer, FrameworkElement frameworkElement)
    {
      Debug.Assert(IsInitialized, "StateManager is not yet initialized.");

      var dockingManager = (DockingManager)frameworkElement;

      PrepareContentsForSaving(dockingManager);

      SaveOpenPanels(writer, dockingManager);
      SaveOpenDocuments(writer, dockingManager);
      dockingManager.SaveLayout(writer);
    }


    private static void SetDocumentContent(DocumentContent documentContent, object data)
    {
      if (!(documentContent is WtfDocumentContent))
      {
        return;
      }

      var wtfDocument = (WtfDocumentContent)documentContent;
      wtfDocument.Data = data;
    }


    private static void PrepareContentsForSaving(DockingManager dockingManager)
    {
      DockableContent[] dockableContents = dockingManager.DockableContents;
      int unnamedPanelCounter = 0;

      foreach (DockableContent dockableContent in dockableContents)
      {
        if (string.IsNullOrEmpty(dockableContent.Name))
        {
          dockableContent.Name = "Panel" + unnamedPanelCounter++;
        }
      }

      List<DocumentPane> documentPanes = GetAllDocumentPanes(dockingManager);
      unnamedPanelCounter = 0;

      foreach (DocumentPane documentPane in documentPanes)
      {
        if (string.IsNullOrEmpty(documentPane.Name))
        {
          documentPane.Name = "DocumentPane" + unnamedPanelCounter++;
        }
      }
    }


    private static List<DocumentPane> GetAllDocumentPanes(DependencyObject dockingManager)
    {
      var documentPanes = new List<DocumentPane>();

      int childrenCount = VisualTreeHelper.GetChildrenCount(dockingManager);

      for (int i = 0; i < childrenCount; i++)
      {
        DependencyObject child = VisualTreeHelper.GetChild(dockingManager, i);

        if (child is DocumentPane)
        {
          documentPanes.Add((DocumentPane)child);
        }
        else
        {
          List<DocumentPane> panes = GetAllDocumentPanes(child);
          documentPanes.AddRange(panes);
        }
      }

      return documentPanes;
    }


    private void SaveOpenDocuments(XmlWriter writer, DockingManager dockingManager)
    {
      writer.WriteStartElement("OpenDocuments");
      {
        ResizingPanel documentPanesHost = FindDocumentPanesHost(dockingManager);

        SaveOpenDocuments(writer, documentPanesHost);
      }
      writer.WriteEndElement();
    }


    private void SaveOpenDocuments(XmlWriter writer, DependencyObject documentPanesHost)
    {
      int childrenCount = VisualTreeHelper.GetChildrenCount(documentPanesHost);
      DocumentPaneResizingPanel resizingPanel = null;

      for (int i = 0; i < childrenCount; i++)
      {
        DependencyObject child = VisualTreeHelper.GetChild(documentPanesHost, i);

        if (child is DocumentPane)
        {
          var documentPane = (DocumentPane)child;

          writer.WriteStartElement("DocumentPane");
          {
            writer.WriteAttributeString("Name", documentPane.Name);

            bool isMainDocumentPane = documentPane.IsMainDocumentPane == null
                                        ? false : (bool)documentPane.IsMainDocumentPane;

            writer.WriteAttributeString("isMainDocumentPane", isMainDocumentPane.ToString());

            SaveOpenDocuments(writer, documentPane);
          }
          writer.WriteEndElement();
          continue;
        }

        if (child is DocumentPaneResizingPanel)
        {
          resizingPanel = (DocumentPaneResizingPanel)child;
        }
      }

      if (resizingPanel != null)
      {
        writer.WriteStartElement("DocumentPaneResizingPanel");
        {
          writer.WriteAttributeString("Name", resizingPanel.Name);
          writer.WriteAttributeString("Orientation", resizingPanel.Orientation.ToString());

          SaveOpenDocuments(writer, resizingPanel);
        }
        writer.WriteEndElement();
      }
    }


    private void SaveOpenDocuments(XmlWriter writer, ItemsControl documentPane)
    {
      foreach (object item in documentPane.Items)
      {
        var document = item as DocumentContent;

        if (document == null)
        {
          continue;
        }

        UIControlInfo contentContainer = _uiService.GetContentContainer(document);

        if (contentContainer == null)
        {
          continue;
        }

        writer.WriteStartElement(contentContainer.ControlType.FullName);
        {
          writer.WriteStartElement("Content");
          {
            SaveDocumentContent(document, writer);
          }
          writer.WriteEndElement();
        }
        writer.WriteEndElement();
      }
    }


    private void SaveDocumentContent(DocumentContent document, XmlWriter writer)
    {
      if (!(document is WtfDocumentContent))
      {
        return;
      }

      var wtfDocument = (WtfDocumentContent)document;
      SaveContent(wtfDocument.Data, writer);
    }


    private void SaveContent(object data, XmlWriter writer)
    {
      foreach (IContentSerializer serializer in Extensions)
      {
        if (!serializer.CanSerialize(data))
        {
          continue;
        }

        serializer.Serialize(data, writer);
        return;
      }
    }


    private static ResizingPanel FindDocumentPanesHost(DockingManager dockingManager)
    {
      DocumentPane mainDocumentPane = dockingManager.MainDocumentPane;

      if (mainDocumentPane != null)
      {
        DependencyObject dependencyObject = mainDocumentPane;

        while (dependencyObject != null && dependencyObject.GetType() != typeof(ResizingPanel))
        {
          dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
        }

        return dependencyObject as ResizingPanel;
      }

      return null;
    }


    private void SaveOpenPanels(XmlWriter writer, DockingManager dockingManager)
    {
      writer.WriteStartElement("OpenPanels");
      {
        DockableContent[] dockableContents = dockingManager.DockableContents;

        foreach (DockableContent dockableContent in dockableContents)
        {
          UIControlInfo panel = _uiService.GetPanel(dockableContent.GetType());

          if (panel != null)
          {
            writer.WriteStartElement(dockableContent.Name);
            writer.WriteAttributeString("TypeName", panel.ControlType.FullName);
            writer.WriteEndElement();
          }
        }
      }
      writer.WriteEndElement();
    }


    protected override void OnStateChnaged()
    {
      if(State == FrameworkComponentState.Initialized)
      {
        _uiService = (UIService)WtfFramework.Instance.Services[typeof(UIService)];
      }
      else
      {
        _uiService = null;
      }
    }


    protected override void OnUnload()
    {
    }


    protected override FrameworkComponentState OnInitialize()
    {
      return FrameworkComponentState.Initialized;
    }


    protected override FrameworkComponentState OnLoad()
    {
      AddDependency(typeof(UIService));

      return FrameworkComponentState.Loaded;
    }


    protected override void OnExtensionRemoved(Service extension)
    {
    }


    protected override void OnExtensionAdded(Service extension)
    {
    }


    public override bool IsExtension(Service service)
    {
      return service is IContentSerializer;
    }


    #endregion



    public Type ControlType
    {
      get
      {
        return typeof(DockingManager);
      }
    }
  }
}